#if (defined(__linux) || defined(linux)) /* Only Linux */

#define FUSE_USE_VERSION 26 /* Set new FUSE-API */
#define _XOPEN_SOURCE 500  /* include pread,pwrite */

/** Errors:

-EFBIG; = File to big
-EMFILE;
-EACCES;
**/

/** C++-Header **/
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>

/** C-Header **/
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

#include <boost/lexical_cast.hpp>
/** FUSE-Header **/
#include <fuse.h>

#include <pthread.h>

/** Filesystem **/
#include "Filesystem.h"

using namespace std;

PFilesystem pFilesystem;
struct fuse_operations operations;


/**
 *
 **/
void Filesystem::setFileHandler(PFSInfo info, PFileHandler fh)
{
	info->fi->fh = (long unsigned int)fh;
}

/**
 *
 **/
PFileHandler Filesystem::getFileHandler(PFSInfo info)
{
    if( info->fi == NULL || info->fi->fh == NULL)
    {
        return NULL;
    }
    else return (PFileHandler)info->fi->fh;
}

/**
 *
 **/
void Filesystem::listFile(String filename, PFSInfo info, size_t filesize) {
    pFilesystem->listDir(filename, info);
}

/**
 *
 **/
void Filesystem::listDir(String dirname, PFSInfo info) {
	info->filler(info->readBuffer, dirname.c_str(), NULL, 0);
}

/**
 *
 **/
void Filesystem::setDirAttribute(PFSInfo info, bool readOnly)
{
    //current User
    info->attributes->st_uid = fuse_get_context()->uid;
    info->attributes->st_gid = fuse_get_context()->gid;

    if(readOnly)
    {
        info->attributes->st_mode = S_IFDIR | 0444;
    }
    else
    {
        info->attributes->st_mode = S_IFDIR | 0777;
    }
}

/**
 *
 **/
void Filesystem::setFileAttribute(PFSInfo info, size_t filesize)
{
    //current User
    info->attributes->st_uid = fuse_get_context()->uid;
    info->attributes->st_gid = fuse_get_context()->gid;

    info->attributes->st_mode = S_IFREG | 0777;
    info->attributes->st_nlink = 1;
    info->attributes->st_blksize = 4096;
    if(info->isCache)
    {
        PMemoryFile data = pFilesystem->getMemoryFile(info->path, info->isCache);
        if(data != NULL) {
            info->attributes->st_size = data->size;
        } else {
            info->attributes->st_size = filesize;
        }
    }
    else info->attributes->st_size = filesize;
    info->attributes->st_blocks = ((info->attributes->st_size + info->attributes->st_blksize - 1) & ~((unsigned long long) info->attributes->st_blksize - 1)) >> 9;
}

/**
 *
 * Notice: This function is called REALLY often.
 **/
int geodbfs_getattr(const char *path, struct stat *stbuf){
    memset(stbuf, 0, sizeof(struct stat));
    FSInfo info = Filesystem::createFSInfo("getattr", path, NULL, NULL, NULL, 0, 0, stbuf, NULL, pFilesystem);
	int ret1 = pFilesystem->getattr( &info );

    if(info.isCache && info.level == 2) {
        if(pFilesystem->getMemoryFile(info.path, true) == NULL && pFilesystem->getFileHandler(&info) == NULL)
        {
            return -ENOENT; // File not exists
        }
    }
    return ret1;
}

/**
 *
 **/
int geodbfs_fgetattr (const char *path, struct stat *stbuf, struct fuse_file_info *fi) {
    memset(stbuf, 0, sizeof(struct stat));
    FSInfo info = Filesystem::createFSInfo("fgetattr", path, NULL, NULL, NULL, 0, 0, stbuf, NULL, pFilesystem);
	return pFilesystem->getattr( &info );
}

/**
 *
 **/
static int geodbfs_readdir(const char *path, void *readBuffer, fuse_fill_dir_t filler,off_t offset, struct fuse_file_info *fi) {
    FSInfo info = Filesystem::createFSInfo("readdir", path, readBuffer, NULL, filler, offset, 0, NULL, fi, pFilesystem);
	return pFilesystem->readDir( &info );
}

/**
 *
 **/
static int geodbfs_open(const char *path, struct fuse_file_info *fi){
    FSInfo info = Filesystem::createFSInfo("open", path, NULL, NULL, NULL, 0, 0, NULL, fi, pFilesystem);

    int ret1 = 0;
    info.createMode = false;
    info.writeMode  = fi->flags & O_WRONLY;
    info.readMode   = fi->flags & O_RDONLY;
    info.rwMode     = fi->flags & O_RDWR;
    info.appendMode = fi->flags & O_APPEND;

	if(info.isCache && info.level == 2)
	{
		if(pFilesystem->getMemoryFile(info.path, true) == NULL)
		{
			ret1 = -ENOENT; // File not exists
		}
	}
    int ret2 = pFilesystem->open( &info );

	if( ret2 != 0) return ret2;
	else return ret1;
}

/**
 *
 **/
static int geodbfs_read(const char *path, char *readBuffer, size_t size, off_t offset, struct fuse_file_info *fi) {
    FSInfo info = Filesystem::createFSInfo("read", path, readBuffer, NULL, NULL, offset, size, NULL, fi, pFilesystem);
	return pFilesystem->read( &info );
}

/**
 *
 **/
static int geodbfs_write(const char *path, const char *writeBuffer, size_t size, off_t offset, struct fuse_file_info *fi) {
    FSInfo info = Filesystem::createFSInfo("write", path, NULL, writeBuffer, NULL, offset, size, NULL, fi, pFilesystem);
	return pFilesystem->write( &info );
}

/**
 *
 **/
static int geodbfs_release(const char * path, struct fuse_file_info *fi) {
    FSInfo info = Filesystem::createFSInfo("release", path, NULL, NULL, NULL, 0,0, NULL, fi, pFilesystem);
	return pFilesystem->close( &info );
}

/**
 *
 **/
static int geodbfs_opendir(const char *path, struct fuse_file_info *fi) {
	FSInfo info = Filesystem::createFSInfo("opendir", path, NULL, NULL, NULL, 0,0, NULL, fi, pFilesystem);
	return pFilesystem->openDir(&info);
}

/**
 *
 **/
static int geodbfs_unlink(const char *path) {
    FSInfo info = Filesystem::createFSInfo("unlink", path, NULL, NULL, NULL, 0,0, NULL, NULL, pFilesystem);
    return pFilesystem->unlink(&info);
}

static int geodbfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
    FSInfo info = Filesystem::createFSInfo("create", path, NULL, NULL, NULL, 0,0, NULL, fi, pFilesystem);
    info.createMode = true;
    info.writeMode  = fi->flags & O_WRONLY;
    info.readMode   = fi->flags & O_RDONLY;
    info.rwMode     = fi->flags & O_RDWR;
    info.appendMode = fi->flags & O_APPEND;
    int ret1 = 0;
    int ret2 = pFilesystem->open( &info );
	if(info.isCache && info.level == 2)
	{
		if(pFilesystem->getMemoryFile(info.path, true) == NULL)
		{
			ret1 = -ENOENT; // File not exists
		}
	}

	if( ret2 != 0) return ret2;
	else return ret1;
}

static int geodbfs_truncate (const char *path, off_t offset) {
    FSInfo info = Filesystem::createFSInfo("truncate", path, NULL, NULL, NULL, 0,0, NULL, NULL, pFilesystem);
    return pFilesystem->truncate(&info);
}

/**
 * not implement
 **/
static int geodbfs_mkdir(const char *path, mode_t mode) { return 0; }
static int geodbfs_rmdir(const char *path) { return 0; }
static int geodbfs_chmod(const char *path, mode_t mode) { return 0; }
static int geodbfs_chown(const char *path, uid_t uid, gid_t gid) { return 0; }
static int geodbfs_utimens (const char *path, const struct timespec tv[2]) { return 0; }
static int geodbfs_mknod (const char *path, mode_t mode, dev_t dev) { return 0; }
static int geodbfs_ftruncate (const char *path, off_t offset,struct fuse_file_info *fi) { return 0; }
static int geodbfs_rename(const char *from, const char *to) { return -EMFILE; }
static int geodbfs_closedir(const char*path, struct fuse_file_info *fi){ return 0;}

int geodbfs_init(int argc, char* argv[], PFilesystem filesystem)
{
    pFilesystem = filesystem;

	operations.opendir = geodbfs_opendir;
    operations.readdir = geodbfs_readdir;
    operations.releasedir = geodbfs_closedir;
    operations.getattr = geodbfs_getattr;
    operations.fgetattr= geodbfs_fgetattr;
    operations.read    = geodbfs_read;  // Read File
    operations.open    = geodbfs_open; // Open FIle
    operations.unlink  = geodbfs_unlink;
    operations.write   = geodbfs_write; // Write File
    operations.release = geodbfs_release; // Close File
    operations.rename  = geodbfs_rename;
    operations.rmdir   = geodbfs_rmdir;
    operations.mkdir   = geodbfs_mkdir;
    operations.chmod   = geodbfs_chmod;
    operations.chown   = geodbfs_chown;
    operations.create  = geodbfs_create;
    operations.utimens = geodbfs_utimens;
    operations.mknod   = geodbfs_mknod;
    operations.mkdir   = geodbfs_mkdir;
    operations.truncate= geodbfs_truncate;
    operations.ftruncate= geodbfs_ftruncate;
    return fuse_main(argc, argv, &operations, NULL);
}


#endif

